<?php
/**
*	Memcache handle class
*/
class KVMemcache{
	
	protected	$site;				# this site
	protected	$cache_name;  		# name of this cache 
	protected	$h_param;         	# extra parameter
	protected	$handle_obj;		# handle obj
	
	private		$memcache_obj ;  			# child cannot inherit
	protected	$compress  = 0;             # 0: no compress, 1 compress
	protected	$threshold = '20000,0.2';   # compress threshold 20kbytes, compress save 20% more
	protected	$expire    = 0;             # no expiry time, but no guarantee  it stay
	
	# Two mode supported Read through ( read from DB when miss) / read-only, set in site_config
	protected	$read_through_mode 	= false; # false: don't read-through from db when get
	protected	$read_only_mode    	= false; #
	protected	$cache_status		= false; # show cache status

	private		$LOGNAME			= '';	 # to log cache name

	function __construct($handle_obj, $h_param = array()){
	
		global $kvcache;	# get config from KVCacheConfig.php

		$this->site		  =	get_hash_param($kvcache,'site','');		#site flag
		$this->handle_obj = $handle_obj;							#外調處理類對象
		$this->expire 	  = $this->handle_obj->get_cache_expire();	#超時時間
		if($this->expire=='-1'){
			$this->expire = get_hash_param($kvcache,'default_expire',0);
		}
		$this->cache_name = $this->handle_obj->get_cache_name();
		$this->h_param    = $h_param;
		$this->compress   = get_hash_param($h_param, 'compress', 0);

		$this->LOGNAME = get_hash_param($kvcache, 'cache_logname' ,'CACHELOGNAME');

		if(get_hash_param($h_param, 'expire', '-1')!='-1'){		//have set expire  param
			$this->expire = get_hash_param($h_param, 'expire', 0);	//have set expire param	
		}

		$this->threshold = get_hash_param($h_param, 'threshold', ''); # string, size,ratio
	
		$h_config = get_hash_param($kvcache, $this->cache_name, ''); # check if not exists
	
		if ($h_config != ''){	# define config
			$memcache_pool = get_hash_param($h_config, 'memcache', ''); 			# get memcache pool
			if($memcache_pool == ''){
				$memcache_pool = get_hash_param($kvcache, 'default_memcache','');	# get default memcache pool
			}
			$mode = get_hash_param($h_config, 'mode', '');							# get mode
			if($mode == ''){
				$mode = get_hash_param($kvcache, 'default_mode','read-only');		# get default mode;
			}

		} else {	# not define config
			$memcache_pool = get_hash_param($kvcache, 'default_memcache','');		# get default memcache pool
			$mode = get_hash_param($kvcache, 'default_mode','read-only');			# get default mode
		}
		
		$this->connect($memcache_pool);	
		if ($mode == 'read-through'){
			$this->read_through_mode = true;
		}else if ($mode == 'read-only'){
			$this->read_only_mode = true;
		}
	}
	
	# this function should not be called from outside, 
	# as it is use the use site_config setting
	protected function connect($a_memcache_server){
		# when to reconnect ?
		if (! isset($this->memcache_obj)){
			$memcache_obj = new Memcache;
			foreach ($a_memcache_server as $server){
				list ($host, $port) = explode(':',$server);
				#echo("memcache $host, $port\n");
				$memcache_obj->connect($host, $port);	
			}
		
			if($this->compress == 1) {
				$threshold = $this->threshold;
				list($threshold,$min_saving) = explode(',', $threshold);
				if (!empty_string($min_saving) && $min_saving >0 && $min_saving<1){
					# string set with min_saving
					$memcache_obj->setCompressThreshold($threshold, $min_saving);	
				} else {
					$memcache_obj->setCompressThreshold($threshold);
				}
			}
			$this->memcache_obj = $memcache_obj;
		}
	}


	function create_cache_key($key_in){
		return $this->site.'_'.$this->cache_name.'_'.$key_in;
	}

	
	# get - a read through
	#       try to get from cache, if not exists, read from db
	function get($key_in){
		$memcache_obj = $this->memcache_obj;
		$key = $this->create_cache_key($key_in);
		$value = $memcache_obj->get($key);
		
		if ($value == ''){	# $value === FALSE
			if ($this->read_through_mode == true){
				$value = $this->create_cache_content($key_in);
				$this->set($key_in, $value);
			} else {
				$value = ''; # non read-through mode, return ''
			}
			$this->cache_status = false;
		}  else {
			# Use result from memcache
			$this->cache_status = true;
		}
		
		if ( method_exists($this, 'post_format') ){
			$value = $this->post_format($key_in, $value);
		}
		return $value;
	}
	
	
	# memcache'd' support get_multi / set multi which has better performance, 
	# so implement in dbm 
	#
	function mget($a_key){
		$a_result = array();
		foreach ( $a_key as $key_in){
			array_push ($a_result, $this->get($key_in));
		}
		return $a_result;
	}

	/* similar to mget, except it return as associative array*/
	function mget_hash($a_key){
		$h_result = array();
		foreach ( $a_key as $key_in){
			$h_result[$key_in] = $this->get($key_in);
		}
		return $h_result;		
	}


	// cache data (in read-only | read-through mode) for later read
	function cache($key_in){
		if ($this->read_through_mode || $this->read_only_mode){
			$value = $this->create_cache_content($key_in);
			$this->set($key_in, $value);
		}
	}
	
	function mcache($a_key){
		foreach ( $a_key as $key_in){
			$this->cache($key_in);
		}
	}


			
	# set will be allowed for both read-only mode
	#     and read-through mode
	function set($key_in, $value, $expire=null){
		$memcache_obj  = $this->memcache_obj;
		$compress_flag = $this->compress == 1 ? MEMCACHE_COMPRESSED : 0;
		$key = $this->create_cache_key($key_in);
		if(isset($expire)){
			$memcache_obj->set($key, $value, $compress_flag, $expire);
		}else{
			if($this->expire > 0) {
				$memcache_obj->set($key, $value, $compress_flag, $this->expire);
			} else {
				$memcache_obj->set($key, $value, $compress_flag);
			}
		}
		if($this->cache_name!=$this->LOGNAME){
			$this->toLog($key_in,$compress_flag);
		}
	}
	
	/* set mulitple item in one API
	* ONLY useful for memcache'd' class.
	*/
	function mset($h_value){
		foreach ($h_value as $key_in => $value){
			$this->set($key_in, $value);
		}
	}
		
	# read from gdbm only, it will not try to create one (unlike get) when not exists
	function read($key_in){
		$memcache_obj = $this->memcache_obj;		
		$key = $this->create_cache_key($key_in);
		$var = $memcache_obj->get($key);
		return $value;
	}
	
	function readall(){
		echo "dbm - readall - this function is not implemented";
		throw new Exception('dbm - readall - this function is not implemented: '.$this->cache_name, 1116);
	}
	
	# $value : value to be increment
	# especially for increment nclick
	function incr($key_in, $value_inc){
		$value = $this->read($key_in);
		$this->set($key_in, $value + $value_inc);
	}

	function decr($key_in, $value_decr){
		$value = $this->read($key_in);
		$this->set($key_in, $value - $value_decr);
	}

	function del($key_in){
		$memcache_obj = $this->memcache_obj;
		$key = $this->create_cache_key($key_in);  	
		return $memcache_obj->delete($key);
	}
  
	function mdel($a_key){
		foreach ($a_key as $key_in){
			$this->del($key_in);
		}
	} 

	# to clear some gdbm, typical for clear it when type table is updated
	#
	function delete_all(){
		echo "dbm - delete_all - this function is not implemented";
		throw new Exception('dbm - delete_all - this function is not implemented: '.$this->cache_name, 1116);
	}

	protected function get_memcache_obj(){
		return $this->memcache_obj;
	}

	function get_cache_status() {
		return $this->cache_status;
	}


	function __destruct() {
		$memcache_obj = $this->memcache_obj;
		$memcache_obj->close();
		if (method_exists($this, 'db_disconnect')){
		  $this->db_disconnect();
		}
	}
	
	# terry add for log memcache key
	protected function toLog($key_in,$compress_flag){
		$memcache_obj = $this->memcache_obj;
		$cache_name = $this->cache_name;

		$result = $memcache_obj->get($this->site.'_'.$this->LOGNAME.'_0');
		if(!is_array($result)){
			$result = array();
		}
		$result[$this->cache_name][$key_in] = $key_in;
		$memcache_obj->set($this->site.'_'.$this->LOGNAME.'_0',$result,$compress_flag);
	}


	# terry add for handle start
	function create_cache_content($key_in){
		return $this->handle_obj->create_cache_content($key_in);
	}
	
	function post_format($key_in, $value){
		if(method_exists($this->handle_obj,'post_format')){
			return $this->handle_obj->post_format($key_in, $value);
		}else{
			return $value;
		}
	}
	# terry add for handle end
}
?>